<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>zanePerfor性能监控统计平台</title>
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
	<style>
		*{margin:0;padding:0;}
		a{color:rgb(135, 118, 247);text-decoration: none;}
		.body{font-size:16px;}
		.header {width:100%;overflow:hidden;margin:0 auto;height:60px;line-height:60px;position: fixed;left:0;top:0;background:#fff;border-bottom:solid 1px #ddd;}
		.inlin-block{display:inline-block;}
		.header .logo{font-size:25px;display:flex;align-items: center;flex-direction: row;width:150px;float:left;margin-left:20px;}
		.header .logo img{width:40px;height:auto;margin-right:10px;}
		.header .right{float:right;text-decoration: none;color:#333;}
		.header .desc{color:#999;margin-left:50px;font-size:13px;}
		.main{border-top:solid 1px #ddd;}
		.side{width:180px;height:100%;padding:20px;border-right:solid 1px #ddd;font-size:14px;position:fixed;left:0;top:60px;}
		.side a{color:#333;text-decoration: none;}
		.side .item{margin-bottom:15px;}
		.side .item.active a{color:rgb(135, 118, 247);}
		.content{overflow:hidden;margin-left:230px;margin-top:60px;padding:30px 30px 100px 130px;max-width:1200px;}
		h1{font-size:30px;}
		h2{font-size:20px;border-bottom:solid 1px #ddd;padding-bottom:10px;}
		h3{font-size:16px;font-weight:bold;}
		p{font-size:14px;line-height:35px;}
		strong{font-weight:bold;font-size:16px;}
		blockquote {padding: 0 10px;color: #777;border-left: 5px solid #ddd;}
		.mt10{margin-top:10px;}
		.mt20{margin-top:20px;}
		.mt30{margin-top:30px;}
		.mr20{margin-right:20px;}
		.mb10{margin-bottom:10px;}
		.mb20{margin-bottom:20px;}
		.mb30{margin-bottom:30px;}
		ul li {list-style: none;font-size:14px;line-height:35px;display:flex;flex-direction: row;align-items: center;}
		ul li:before{content:'';display:inline-block;width:5px;height:5px;border-radius:100%;background:#ccc;margin-right:8px;}
		.red{color:red;}
		.toggle_slide{display:none;}
		img{max-width:100%;}
		@media (max-width: 768px) {
			.toggle_slide{display:block;width:60px;height:60px;text-align:center;float:left;cursor: pointer;}
			.toggle_slide img{width:40px;height:40px;margin-top:10px;}
			.side{display: none;background:#fff;top:61px;}
			.content{margin-left: 0px;padding: 30px 20px 100px 20px;}
			.header .logo{margin-left:0;}
			.header .desc{display:none;}
			.header .right{margin-right:8px;font-size:14px;}
		}
	</style>
	<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/themes/prism-okaidia.css" rel="stylesheet">
	<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/prism.min.js"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-javascript.min.js"></script>
	<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
	<script>$(function () {$(".toggle_slide").click(function () {if($(".side").css("display")=='block'){$(".side").css("display", "none");}else{$(".side").css("display", "block");}});})</script>
</head>
<body>
	<div class="header">
		<div class="toggle_slide"><img src="https://cdn.seosiwei.com/Sb4GfHisSa5Wsbka2anshJjhTmsTBrCK.png?imageslim"></div>
		<div class="inlin-block logo">
			<img src="https://img.allpyra.com/d5d9737d-8111-4c1e-8f03-0913ec1b2c51.png" alt="logo">
			<div>zanePerfor</div>
		</div>
		<div class="inlin-block desc">前端性能监控业务平台</div>
		<a class="right mr20" target="_blank" href="https://github.com/wangweianger/zanePerfor">GITHUB地址</a>
		<a class="right mr20" target="_blank" href="https://blog.seosiwei.com/">我的博客</a>
	</div>
	<div class="main">
		<div class="side">
			<div class="item active" data-mark="index"><a href="/performance/index.html">zanePerfor是什么?</a></div>
			<div class="item" data-mark="project"><a href="/performance/project.html">项目服务架构</a></div>
			<div class="item" data-mark="SDK"><a href="/performance/SDK.html">上报SDK</a></div>
			<div class="item" data-mark="simple_deployment"><a href="/performance/simple_deployment.html">快速部署入门体验</a></div>
			<div class="item" data-mark="configs"><a href="/performance/configs.html">项目配置说明</a></div>
			<div class="item" data-mark="tasks"><a href="/performance/tasks.html">定时任务说明</a></div>
			<div class="item" data-mark="repeart_task"><a href="/performance/repeart_task.html">集群定时任务避免重复执行</a></div>
			<div class="item" data-mark="iptask"><a href="/performance/iptask.html">IP解析地址性能说明</a></div>
			<div class="item" data-mark="oneservers"><a href="/performance/oneservers.html">单机部署服务稳定说明</a></div>
			<div class="item" data-mark="github"><a href="/performance/github.html">github OAuth</a></div>
			<div class="item"><a href="/performance/wait.html">redis集群搭建</a></div>
			<div class="item"><a href="/performance/wait.html">Mongodb索引优化</a></div>
			<div class="item" data-mark="replica_set"><a href="/performance/replica_set.html">Mongodb副本集读写分离</a></div>
			<div class="item" data-mark="colony"><a href="/performance/colony.html">Mongodb集群分片</a></div>
			<div class="item"><a href="/performance/wait.html">servers(负载均衡)</a></div>
			<div class="item"><a href="/performance/wait.html">消息队列升级改造</a></div>
			<div class="item"><a href="/performance/wait.html">未来规划</a></div>
		</div>
		<div class="content">
			<h1 class="mb20">Mongodb副本集读写分离</h1>
			<h2 class="mb10 mt20">优势：</h2>
			<p>MongoDB 副本集（Replica Set）是有自动故障恢复功能的主从集群，有一个Primary节点和一个或多个Secondary节点组成。</p>
			<p>当主节点挂掉之后，会由多个副本节点选举产生出新的主节点。（节点数请保持为基数个）。</p>
			<p>这样就能保证应用的高可用，其中一个或多个节点挂掉之后还能正常运行和服务。</p>
			<h2 class="mb10 mt20">劣势：</h2>
			<p>数据丢失：主节点挂掉之后，副本节点选举出新的主节点需要一定的时间，这段时间会造成数据的丢失。推选出的新主节点立即挂掉之后会有30秒的空档期，也会造成数据的丢失。</p>	
			<p>不能承受高吞吐量：副本集架构追根还是单机执行写任务，在高并发应用中性能受限，不能解决流量洪峰时的实时读写。</p>
			<p>不能完全保证项目的高可用：在副本集的环境中，要是所有的Secondary都宕机了，只剩下Primary。最后Primary会变成Secondary，将不能再提供服务。</p>
			<h2 class="mb10 mt20">总结：</h2>
			<p>在大多数情况下推荐使用副本集架构，副本集架构在保证高可用的同时还能降低服务器成本，相对于集群分片来说配置也更简单，更易于维护，具体选择什么架构需要根据自己的项目来决定。</p>
			<h2 class="mt20">Mongodb副本集架构搭建：</h2>
			<blockquote class="mt10">
				<p>备注：Mongodb副本集搭建比较简单，你只需要根据下面的步骤一步一步操作即可（以下内容以Linux或mac为例进行环境搭建）。</p>
			</blockquote>
			<h2 class="mt20">一：安装Mongodb （略）</h2>
			<p>请参考：<a target="_blank" href="https://blog.seosiwei.com/detail/40">LINUX系统下安装mongodb</a></p>
			<p>关于副本集搭建还可参考我的另一篇文章： <a target="_blank" href="https://blog.seosiwei.com/detail/39"></a>MongoDB主从副本集架构</p>
			<h2 class="mt20">二：副本集搭建</h2>
			<blockquote class="mt10">
				<p>备注：鉴于成本，以下内容在单机下部署为例，多机部署只需要替换下IP即可</p>
			</blockquote>
			<h3 class="mt10">1、创建数据和日志存放目录</h3>
<pre><code class="language-javascript">
// 数据存放目录
mkdir -p /data/replication/s0
mkdir -p /data/replication/s1
mkdir -p /data/replication/s2

// 日志存放目录
mkdir -p /data/replication/log
</code></pre>
			<h3 class="mt10">2、启动Mongodb服务</h3>
			<blockquote class="mt10">
				<p>备注：下面以28100，28101，28100三个端口为例</p>
			</blockquote>
<pre><code class="language-javascript">
// 启动mongodb服务
mongod --dbpath /data/replication/s0 --logpath /data/replication/log/s0.log --fork --smallfiles --port 28100 --replSet rs1
mongod --dbpath /data/replication/s1 --logpath /data/replication/log/s1.log --fork --smallfiles --port 28101 --replSet rs1
mongod --dbpath /data/replication/s2 --logpath /data/replication/log/s2.log --fork --smallfiles --port 28102 --replSet rs1
</code></pre>
			<ul class="mt10">
				<li>--dbpath：存放数据目录</li>
				<li>--logpath：存放日志目录</li>
				<li>--smallfiles：是否使用较小的默认文件。默认为false，不使用。</li>
				<li>--replSet: 副本集名称，副本集名称必须一致</li>
			</ul>
			<h3 class="mt10">3、进入28100服务设置副本集</h3>
<pre><code class="language-javascript">
// 登录 mongodb
mongo localhost:28100

// 切换到admin用户
use admin

// 初始化副本集
rs.initiate({_id:"rs1",members:[{_id:0,host:"127.0.0.1:28100"},{_id:1,host:"127.0.0.1:28101"},{_id:2,host:"127.0.0.1:28102"}]})

// 查看副本集状态
rs.status()
</code></pre>
			<p>副本集设置成功之后，查看状态会看到如下信息即标识成功</p>
<pre><code class="language-javascript">
{
	"set" : "rs1",
	"date" : ISODate("2018-11-14T08:40:44.659Z"),
	"myState" : 1,
	"term" : NumberLong(2),
	"heartbeatIntervalMillis" : NumberLong(2000),
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1542184835, 1),
			"t" : NumberLong(2)
		},
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1542184835, 1),
			"t" : NumberLong(2)
		},
		"appliedOpTime" : {
			"ts" : Timestamp(1542184835, 1),
			"t" : NumberLong(2)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1542184835, 1),
			"t" : NumberLong(2)
		}
	},
	"members" : [
		{
			"_id" : 0,
			"name" : "127.0.0.1:28100",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 5977,
			"optime" : {
				"ts" : Timestamp(1542184835, 1),
				"t" : NumberLong(2)
			},
			"optimeDate" : ISODate("2018-11-14T08:40:35Z"),
			"electionTime" : Timestamp(1542178880, 1),
			"electionDate" : ISODate("2018-11-14T07:01:20Z"),
			"configVersion" : 1,
			"self" : true
		},
		{
			"_id" : 1,
			"name" : "127.0.0.1:28101",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 5970,
			"optime" : {
				"ts" : Timestamp(1542184835, 1),
				"t" : NumberLong(2)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1542184835, 1),
				"t" : NumberLong(2)
			},
			"optimeDate" : ISODate("2018-11-14T08:40:35Z"),
			"optimeDurableDate" : ISODate("2018-11-14T08:40:35Z"),
			"lastHeartbeat" : ISODate("2018-11-14T08:40:43.345Z"),
			"lastHeartbeatRecv" : ISODate("2018-11-14T08:40:43.603Z"),
			"pingMs" : NumberLong(0),
			"syncingTo" : "127.0.0.1:28102",
			"configVersion" : 1
		},
		{
			"_id" : 2,
			"name" : "127.0.0.1:28102",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 5970,
			"optime" : {
				"ts" : Timestamp(1542184835, 1),
				"t" : NumberLong(2)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1542184835, 1),
				"t" : NumberLong(2)
			},
			"optimeDate" : ISODate("2018-11-14T08:40:35Z"),
			"optimeDurableDate" : ISODate("2018-11-14T08:40:35Z"),
			"lastHeartbeat" : ISODate("2018-11-14T08:40:43.345Z"),
			"lastHeartbeatRecv" : ISODate("2018-11-14T08:40:43.575Z"),
			"pingMs" : NumberLong(0),
			"syncingTo" : "127.0.0.1:28100",
			"configVersion" : 1
		}
	],
	"ok" : 1,
	"operationTime" : Timestamp(1542184835, 1),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1542184835, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
</code></pre>
			<h3 class="mt10">4、设置Mongodb副本可读</h3>
			<blockquote class="mt10">
				<p>备注：在mac和linux系统中，一般在~目录下会有个.mongorc.js文件，给此文件新增一句rs.slaveOk();即可。</p>
			</blockquote>
			<p>​查看是否有此文件：</p>
<pre><code class="language-javascript">
cd ~
ll -a
</code></pre>
			<p>若无，则全局查找：</p>
<pre><code class="language-javascript">
// 全局搜索
sudo find / -name .mongorc.js
</code></pre>
			<p>添加rs.slaveOk();</p>
<pre><code class="language-javascript">
vi ~/.mongorc.js

// 此文件默认为空
// 增加一行,保存退出
rs.slaveOk();
</code></pre>
			<p>重启Mongodb，这时所有副本节点都可读。</p>
			<h2 class="mt20">在zanePerfor (前端性能监控平台)生产环境中使用，并做读写分离。</h2>
			<h3 class="mt10">一：找到项目的 config/config.prod.js文件</h3>
			<p>更改如下Mongodb配置即可：</p>
<pre><code class="language-javascript">
// mongodb 服务
// 此处替换 url 参数为链接副本集方式即可
const dbclients = {
	db3: {
		url: 'mongodb://127.0.0.1:28100,127.0.0.1:28101,127.0.0.1:28102/performance?replicaSet=rs1',
		options: {
			poolSize: 100,
			keepAlive: 10000,
			connectTimeoutMS: 10000,
			autoReconnect: true,
			reconnectTries: 100,
			reconnectInterval: 1000,
		},
	},
};
</code></pre>
			<h3 class="mt10">二：读写分离：</h3>
			<p>项目所有查询已经做好了读写分离操作，例如查询page页列表，其他皆如此即可，这样就保证了服务的读写压力（主节点负责写数据，副本节点负责读取数据）。</p>
<pre><code class="language-javascript">
// 此查询增加 .read('sp') 即可
const datas = Promise.resolve(
	this.ctx.model.Web.WebPages.aggregate([
		queryjson,
		{ $sort: { create_time: -1 } },
		{ $skip: ((pageNo - 1) * pageSize) },
		{ $limit: pageSize },
	]).read('sp').exec()
);
</code></pre>
			<p>read参数说明</p>
<pre><code class="language-javascript">
primary - (默认值) 只从主节点读取。如果主节点不可用则报错。不能跟 tags 选项组合使用。
secondary 只有当从节点可用时，从中读取，否则报错。
primaryPreferred 优先读取主节点，不可用时读取从节点。
secondaryPreferred 优先读取从节点，不可用时读取主节点。
nearest 所有操作都读最近的候选节点，不同于其他模式，该选项会随机选取所有主、从节点。
</code></pre>
			<p>选项别名：</p>
<pre><code class="language-javascript">
p primary
pp primaryPreferred
s secondary
sp secondaryPreferred
n nearest
</code></pre>

	</div>
	<script>
		var href = location.href;
		var liobjs = $('.side').find('div')
		for (var i = 0, len = liobjs.length; i < len; i++) {
			var attrr = $(liobjs[i]).attr('data-mark')
			if (href.indexOf(attrr) != -1) {
				liobjs.removeClass('active')
				$(liobjs[i]).addClass('active')
			}
		}
	</script>
</body>
</html>